package com.example.demo.controller;

import ch.qos.logback.core.rolling.helper.IntegerTokenConverter;
import com.alibaba.fastjson.JSON;
import com.example.demo.entity.Product;
import com.example.demo.service.ProdService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redisDemo/")
public class redisController {

    @Autowired
    RedisTemplate redisTemplate;


    @Autowired
    RedissonClient redissonClient;


    @Autowired
    ProdService prodService;

    //JVM级别的内存Map
    private static Map<String,Object>jvmMap=new HashMap<>();



    //缓存过期的时间
    private Integer cacheTimeOut = 1 * 60 * 60;

    private String prodLockKeyPrefix = "guli:mall:lock:";

    //防止缓存穿透缓存的null值
    private String nullKey = "{}";
    //商品缓存的前缀
    private String prodCacheKeyPrefix = "guli:mall:";


    private static String goodsKey = "goodStock";


    //上架秒杀商品
    @RequestMapping("upGoods/{goodId}")
    public String upGoods(@PathVariable("goodId") Integer goodCount) {
//        String stockKey=goodId;
        redisTemplate.opsForValue().set(goodsKey, goodCount.toString());
        return "up---Ok";
    }


    @RequestMapping("getCount")
    public Object getGoodCount() {
        String o = (String) redisTemplate.opsForValue().get(goodsKey);
        return o;
    }


    //原生的redis实现
    @RequestMapping("reduceStock")
    public String testLockWare() {
//        redisTemplate.opsForValue().set("name","李潇潇");
        //使用redis做为分布式锁
        //商品编号（应该为动态取出来的）
        String lockKey = "100";
        String lockValue = "111";
        //尝试获取，能够抢到锁，返回true/false
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
        if (!flag) {
            return "服务器繁忙/占锁失败";
        }


        //抢到了锁，继续以后的流程
        String count = (String) redisTemplate.opsForValue().get(goodsKey);
        Integer goodNum = Integer.parseInt(count);

        try {
            if (goodNum > 0) {
                redisTemplate.opsForValue().set(goodsKey, (Integer.parseInt(count) - 1) + "");
            }

        } catch (Exception exception) {

        } finally {
            //执行删锁的逻辑
            //防止误删锁
            if (redisTemplate.opsForValue().get(lockKey).equals(lockValue)) {
                redisTemplate.delete(lockKey);
            }
        }


        return "reduceStock Success";
    }

    //redis的主从架构间可能存在锁丢失的问题 (主要是发生在主从之间进行同步数据的时候，可能宕机，可以用redLock来解决)

    /**
     * redLock实现的方案为在客户端进行加锁的时候，在集群环境下，超过半数的节点加锁成功，才会真正的加锁成功
     * 假如说现在有三台redis节点，我们在去加锁的时候，必须保证有半数以上的节点加锁成功了以后才是真正的加锁成功，
     * 如果现在有节点挂了的话，那么我们再去加锁
     * 如果只剩下一台机器的话，那么就加不成功了
     * <p>
     * <p>
     * aof去同步的时候。
     */


    //测试Redisson
    @RequestMapping("getRedisson")
    public String testRedissonClient() {

        String lockKey = "goodKey";
        RLock lock = redissonClient.getLock(lockKey);
        String count = (String) redisTemplate.opsForValue().get(goodsKey);
        if (StringUtils.isEmpty(count)) {
            return "缓存的key为空";
        }
        int goodCount = Integer.parseInt(count);
        //上锁
        lock.lock();
        try {
            if (goodCount > 0) {
                goodCount--;
                redisTemplate.opsForValue().set(goodsKey, goodCount + "");
            }
        } catch (Exception exception) {
            return "上锁失败，出现了异常";
        } finally {
            //解锁
            lock.unlock();
        }
        return "分布式锁业务执行完成";
    }


    //测试缓存注解
    @Cacheable()
    public String testCacheAnnotation() {

        return "111";


    }




    //查询商品数据
    @RequestMapping("query/{prodId}")
    public Product testQuery(@PathVariable("prodId") String prodId) {
        //商品缓存的key
        String prodCacheKey = prodCacheKeyPrefix + prodId;
        //上来先去jvm的map中获取一下数据，如果有的话就直接返回
        //JVM级别的缓存怎么保证在分布式系统下的数据一致性（使用MQ，修改了缓存以后，消费者就去去更新map中的数据）
        String res = (String) jvmMap.get(prodCacheKey);
        if (!StringUtils.isEmpty(res)){
            return JSON.parseObject(res,Product.class);
        }


        Product product=null;
        product = getProdFromCache(prodId);

        if (product!=null){
            return product;
        }


        //冷门商品突然大并发量请求进来，防止一下压爆缓存.采用DCL。并且锁的粒度为商品的编号，防止阻塞其他的线程操作其他商品
        RLock lock = redissonClient.getLock(prodLockKeyPrefix + prodId);
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(prodLockKeyPrefix + prodId);
        //获取读锁
        RLock readLock = readWriteLock.readLock();
        //上锁：自动会进行续期，三分之一时间的时候会进行续期。底层采用lua脚本进行编写
        readLock.lock();
        try {
            //再查一次
            product = getProdFromCache(prodId);
            //如果此时缓存中已经有值了，那么就直接去返回
            if (product!=null){
                return product;
            }




            //查询数据库
            product = prodService.getProd();
            //数据库也没有。缓存穿透，缓存null值
            if (product == null) {

                redisTemplate.opsForValue().set(prodCacheKey, nullKey, cacheTimeOut, TimeUnit.SECONDS);
            }

            //在JVM缓存级别设置上商品数据
            jvmMap.put(prodCacheKey,product);
            //设置正式数据
            redisTemplate.opsForValue().set(prodCacheKey, product, cacheTimeOut, TimeUnit.SECONDS);

        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            //释放锁
            readLock.unlock();
        }

        return product;
    }


    //更新商品数据
    @RequestMapping("updateProd")
    public String updateProd(Product product){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(prodLockKeyPrefix + product.getProdId());
        RLock writeLock = readWriteLock.writeLock();
        //使用写锁来加锁
        writeLock.lock();
        try {
            //更新完数据库
            prodService.updateProd(product);

            //商品缓存的key
            String prodCacheKey = prodCacheKeyPrefix + product.getProdId();
            //更新缓存
            redisTemplate.opsForValue().set(prodCacheKey,product,cacheTimeOut,TimeUnit.SECONDS);
            return "更新缓存成功。。。Success";

        }finally {
            writeLock.unlock();
        }

    }



    public Product getProdFromCache(String prodId) {
        //商品缓存的key
        String prodCacheKey = prodCacheKeyPrefix + prodId;
        //先从缓存中进行查找
        String res = (String) redisTemplate.opsForValue().get(prodCacheKey);
        //缓存中不为空的情况
        if (!StringUtils.isEmpty(res)) {
            //缓存穿透
            if (res.equals(nullKey)) {
                //给null值进行延期(防止设置更多的空缓存导致撑爆)
                redisTemplate.opsForValue().set(prodCacheKey, nullKey, cacheTimeOut, TimeUnit.SECONDS);
                return new Product();
            }

            //真实数据延期(针对于热点数据进行延期操作)
            redisTemplate.opsForValue().set(prodCacheKey, res, cacheTimeOut, TimeUnit.SECONDS);
            return JSON.parseObject(res, Product.class);

        }
        return null;
    }








}
